import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.reflect.TypeToken;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Type;


import java.util.ArrayList;
import java.util.Scanner;
import java.util.logging.FileHandler;
//import java.util.logging.Logger;
import org.joda.time.DateTime;



public class Storage {

	static Gson gson;
	ArrayList<Task> tasks;
	static File taskFile;
	static File recoveryFile;
	static File helpFile;
	static final String HELP_FILE_NAME = "help.txt";
	static final String TASK_FILE_NAME = "taskfile.txt";
	static final String RECOVERY_FILE_NAME = "recoveryfile.txt";
	
	
	//private static Logger logger = Logger.getLogger("Storage");
	FileHandler filehandler = new FileHandler("Storage_LogFile.log",false);
	
	static class DateTimeTypeConverter implements JsonSerializer<DateTime>,
			JsonDeserializer<DateTime> {
		// No need for an InstanceCreator since DateTime provides a no-args
		// constructor
		// private static final DateTimeFormatter DTF =
		// DateTimeFormat.forPattern("dd/MM/yyyy");

		@Override
		public JsonElement serialize(DateTime src, Type srcType,
				JsonSerializationContext context) {
			return new JsonPrimitive(src.toString());
		}

		@Override
		public DateTime deserialize(JsonElement json, Type type,
				JsonDeserializationContext context) throws JsonParseException {
			return new DateTime(json.getAsString());
		}
	}

	private static File openFile(String fileName) throws IOException {
		File file = null;

		try {
			file = new File(fileName);

			if (!file.exists()) {
				file.createNewFile();
			}
		} catch (IOException e) {
			System.out.println("Error in opening file");
			e.printStackTrace();
			System.exit(1);
		}

		return file;
	}

	// Constructor
	public Storage() throws IOException, ClassNotFoundException, InterruptedException {
		taskFile = openFile(TASK_FILE_NAME);
		recoveryFile = openFile(RECOVERY_FILE_NAME);
		//setHidden(recoveryFile);
		
		gson = initialiseGson();
		writeToTasks();

	}

	private Gson initialiseGson() {
		GsonBuilder builder = new GsonBuilder();
		builder.registerTypeAdapter(DateTime.class, new DateTimeTypeConverter());
		return builder.setPrettyPrinting().create();

	}

	public String writeToStorage(ArrayList<Task> taskList) throws IOException {
		try {
			
			String json = gson.toJson(taskList);
			FileWriter fw = new FileWriter(TASK_FILE_NAME);
			BufferedWriter writer = new BufferedWriter(fw);
			writer.write(json);
			writer.close();

			return "saved";
		} catch (IOException e) {
			System.out.println("Error occurred in writing to storage.\n");

			e.printStackTrace();
			return "not saved";
		}
	}

	public void writeToRecovery(ArrayList<Task> taskList) throws IOException {
		try {
			recoveryFile.setWritable(true);
			String json = gson.toJson(taskList);
			FileWriter fw = new FileWriter(recoveryFile);
			BufferedWriter writer = new BufferedWriter(fw);
			writer.write(json);
			writer.close();
			// mark recoveryFile as read only
			recoveryFile.setReadOnly();

		} catch (IOException e) {
			System.out.println("Error occurred in writing to recovery file.\n");

			e.printStackTrace();

		}
	}

	public void writeToTasks() throws IOException, ClassNotFoundException {

		try {

			FileReader fr = new FileReader(taskFile);
			BufferedReader reader = new BufferedReader(fr);

			tasks = gson.fromJson(reader, new TypeToken<ArrayList<Task>>() {
			}.getType());

			reader.close();

			if (tasks == null) {
				tasks = new ArrayList<Task>();
			}

		} catch (IOException e) {
			e.printStackTrace();
		} catch (RuntimeException ex) {
			ex.printStackTrace();
			System.out.println("Storage file is corrupted. File content will be restored to previous session.");
			refreshFile();
		}
	}

	public ArrayList<Task> getTasks() {
		return tasks;
	}

	// This method will be called when user make invalid changes to the task
	// file causing it to be corrupted.
	// The task file will be emptied first.
	// The content of recovery file will then be written to task file.
	// The result is to cause the state of the task file to return to last
	// session.
	private void refreshFile() throws IOException, ClassNotFoundException {
		try {
			// empty taskFile
			PrintWriter pw = new PrintWriter(taskFile);
			pw.close();
			recoveryFile.setWritable(true);
			// restore tasks from recoveryFile
			taskFile = new File(RECOVERY_FILE_NAME);
			writeToTasks();
			recoveryFile.setReadOnly();
			
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("Error occurred in refreshing File");
		}
	}

	
	// export task file to desktop.
	public static boolean exportTaskFile() throws IOException {
		String desktopPath = WindowsUtils.getCurrentUserDesktopPath();
		
		try {
			File destinationFile = new File(desktopPath + "\\" +TASK_FILE_NAME);
			FileInputStream fis = new FileInputStream(taskFile);
			FileOutputStream fos = new FileOutputStream(destinationFile);
			byte[] buffer = new byte[2048];
			int length;
			while((length = fis.read(buffer)) > 0) {
				fos.write(buffer,0,length);
			}
			fis.close();
			fos.close();
			return true;
			
		} catch (IOException e) {
			e.printStackTrace();
			return false;
			
		}
	}
	


	
	public static String getHelp() throws IOException {
		StringBuilder fileContents = null;
		Scanner scanner = null;
		try {
			helpFile = openFile(HELP_FILE_NAME);
			helpFile.setReadOnly();
			fileContents = new StringBuilder((int) helpFile.length());
			scanner = new Scanner(new BufferedReader(new FileReader(helpFile)));
			String lineSeparator = "\n";

			while (scanner.hasNextLine()) {
				fileContents.append(scanner.nextLine()).append(lineSeparator);
			}

		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			scanner.close();
		}

		return fileContents.toString();
	}

}
